package game;

import cards.Card;
import cards.Card.Suit;
import cards.Card.Face;
import cards.CardDeck;

import java.util.ArrayList;
import java.util.Comparator;
import java.util.Collections;
import java.util.EnumMap;
import java.util.EnumSet;
import java.util.Map;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;

public class TexasHoldem implements CardDeckGame {
  public static final int HOLE_CARDS = 2;
  public static final int BURN_CARDS = 3;
  public static final int FLOP_CARDS = 3;
  public static final int COMMUNITY_CARDS = 5;
  public static final int MAX_PLAYERS = 10;
  public static final int MIN_PLAYERS = 2;

  private static final Comparator<HandRank> handRankComparator =
      new PokerHandRank.PokerHandRankComparator();

  private static final Comparator<PokerPlayer> playerRankComparator =
      new PokerHandRank.PlayerHandRankComparator();

  protected int playerCount;
  protected CardDeck deck;
  protected List<PokerPlayer> players;
  protected List<Card> burn;
  protected List<Card> comm;

  public TexasHoldem(int players) {
    this.playerCount = players;
    this.players = new ArrayList<PokerPlayer>();
    this.burn = new ArrayList<Card>();
    this.comm = new ArrayList<Card>();
    this.deck = new CardDeck();

    if (players < MIN_PLAYERS || MAX_PLAYERS > 10) {
      throw new IllegalArgumentException("Invalid number of players. "
          + "Players must be between " + MIN_PLAYERS + " and " + MAX_PLAYERS);
    }
  }

  public synchronized void initGame() {
    for (int i = 0; i < playerCount; i++) {
      addPlayer(i);
    }
  }

  public synchronized void runGame() {
    dealBeginning();

    for (int i = 0; i < HOLE_CARDS; i++) {
      for (PokerPlayer p : players) {
        Card c = deck.draw();
        p.addCard(c);
        holeCardDealt(p, c);
      }
    }

    ratePlayerCards();
    holeCardsDealt();
    // bet

    burn.add(deck.draw());
    comm.add(deck.draw());
    comm.add(deck.draw());
    comm.add(deck.draw());

    ratePlayerCards();
    flopDealt();
    // bet

    burn.add(deck.draw());
    comm.add(deck.draw());
    ratePlayerCards();
    turnDealt();
    // bet

    burn.add(deck.draw());
    comm.add(deck.draw());
    ratePlayerCards();
    riverDealt();
    // bet

    // reveal
    PokerPlayer winner = pickWinner();
    revealWinner(winner);
  }

  protected void addPlayer(int playerNum) {
    // TODO: table position should be separate.
    this.players.add(new PokerPlayer(playerNum, playerNum));
  }

  protected void dealBeginning() {
    for (int i = 0; i < playerCount; i++) {
      players.get(i).resetCards();
    }
    burn.clear();
    comm.clear();
    deck.shuffle();
    System.out.println("Dealing holecards to "+players.size()+" players.");
  }

  protected void holeCardDealt(PokerPlayer p, Card c) {
  }

  protected void holeCardsDealt() {
    for (PokerPlayer p : players) {
      System.out.println(p);
    }
  }

  protected void flopDealt() {
    System.out.print("Flop: ");
    for (int i = 0; i < FLOP_CARDS; i++) {
      System.out.print(comm.get(i)+" ");
    }
    System.out.println();
  }

  protected void turnDealt() {
    System.out.println("Turn: "+comm.get(3));
  }

  protected void riverDealt() {
    System.out.println("River: "+comm.get(4));
  }

  protected void revealWinner(PokerPlayer p) {
    System.out.println("WINNER: " + p + " with " + p.privateHandRank());
    displayPlayerRankings();
  }

  protected void displayPlayerRankings() {
    for (PokerPlayer p : players) {
      System.out.println(p + " has ranking: " + p.privateHandRank());
    }
  }

  protected void ratePlayerCards() {
    HandRank pub = PokerHandRank.NOTHING;
    Set<Card> commCards = new TreeSet<Card>(comm);
    for (HandRank ranking : PokerHandRank.values()) {
      if (ranking.hasRank(commCards) &&
          handRankComparator.compare(ranking, pub) > 0) {
        pub = ranking;
      }
    }
    for (PokerPlayer p : players) {
      HandRank priv = PokerHandRank.NOTHING;
      Set<Card> playerCards = new TreeSet<Card>(p.getAllCards());
      playerCards.addAll(commCards);
      for (HandRank ranking : PokerHandRank.values()) {
        if (ranking.hasRank(playerCards) &&
            handRankComparator.compare(ranking, priv) > 0) {
          priv = ranking;
        }
      }
      p.setHandRanks(pub, priv);
    }
  }

  protected PokerPlayer pickWinner() {
    List<PokerPlayer> rankedPlayers = new ArrayList<PokerPlayer>(players);
    Collections.sort(rankedPlayers, playerRankComparator);
    return rankedPlayers.get(rankedPlayers.size() - 1);
  }

  public static void main(String[] args) {
     TexasHoldem g = null;
     int players = 2;

     if (args.length == 1) {
        try {
           players = Integer.parseInt(args[0]);
        } catch (NumberFormatException e) {
           System.err.println("Invalid number of players. Please enter 2 - 10.");
           System.exit(1);
        }
     }
     
     g = new TexasHoldem(players);
     g.initGame();
     g.runGame();
  }
}
